/*
 * Copyright (C) 2016 Philippe Gerum <rpm@xenomai.org>
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *  
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
#include <stdio.h>
#include <error.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <smokey/smokey.h>
#include <rtdm/gpio.h>

smokey_test_plugin(interrupt,
           SMOKEY_ARGLIST(
               SMOKEY_STRING(device),
               SMOKEY_STRING(trigger),
               SMOKEY_BOOL(select),
           ),
   "Wait for interrupts from a GPIO pin.\n"
   "\tdevice=<device-path>\n"
   "\trigger={edge[-rising/falling/both], level[-low/high]}\n"
   "\tselect, wait on select(2)."
);

smokey_test_plugin(read_value,
           SMOKEY_ARGLIST(
               SMOKEY_STRING(device),
           ),
   "Read GPIO value.\n"
   "\tdevice=<device-path>."
);

smokey_test_plugin(write_value,
           SMOKEY_ARGLIST(
               SMOKEY_STRING(device),
           ),
   "Write GPIO value.\n"
   "\tdevice=<device-path>."
);

static int run_interrupt(struct smokey_test *t, int argc, char *const argv[])
{
    static struct {
        const char *name;
        int flag;
    } trigger_types[] = {
        { .name = "edge", .flag = GPIO_TRIGGER_EDGE_RISING },
        { .name = "edge-rising", .flag = GPIO_TRIGGER_EDGE_RISING },
        { .name = "edge-falling", .flag = GPIO_TRIGGER_EDGE_FALLING },
        { .name = "edge-both", .flag = GPIO_TRIGGER_EDGE_FALLING|GPIO_TRIGGER_EDGE_RISING },
        { .name = "level", .flag = GPIO_TRIGGER_LEVEL_LOW },
        { .name = "level-low", .flag = GPIO_TRIGGER_LEVEL_LOW },
        { .name = "level-high", .flag = GPIO_TRIGGER_LEVEL_HIGH },
        { NULL, 0 },
    };
    int do_select = 0, fd, ret, trigger, n, value;
    const char *device = NULL, *trigname;
    fd_set set;
    
    smokey_parse_args(t, argc, argv);

    if (!SMOKEY_ARG_ISSET(interrupt, device)) {
        warning("missing device= specification");
        return -EINVAL;
    }

    device = SMOKEY_ARG_STRING(interrupt, device);
    fd = open(device, O_RDWR);
    if (fd < 0) {
        ret = -errno;
        warning("cannot open device %s [%s]",
            device, strerror(ret));
        return ret;
    }

    if (SMOKEY_ARG_ISSET(interrupt, select))
        do_select = SMOKEY_ARG_BOOL(interrupt, select);

    trigger = GPIO_TRIGGER_NONE;
    if (SMOKEY_ARG_ISSET(interrupt, trigger)) {
        trigname = SMOKEY_ARG_STRING(interrupt, trigger);
        for (n = 0; trigger_types[n].name; n++) {
            if (strcmp(trigger_types[n].name, trigname) == 0) {
                trigger = trigger_types[n].flag;
                break;
            }
        }
        if (trigger == GPIO_TRIGGER_NONE) {
            warning("invalid trigger type %s", trigname);
            return -EINVAL;
        }
    }

    ret = ioctl(fd, GPIO_RTIOC_IRQEN, &trigger);
//ret=0;    
 if (ret) {
        ret = -errno;
        warning("GPIO_RTIOC_IRQEN failed on %s [%s]",
            device, strerror(ret));
        return ret;
    }

    FD_ZERO(&set);
    FD_SET(fd, &set);
    
    for (;;) {
        if (do_select) {
            ret = select(fd + 1, &set, NULL, NULL, NULL);
            if (ret < 0) {
                ret = -errno;
                warning("failed listening to %s [%s]",
                    device, strerror(ret));
                return ret;
            }
        }
        ret = read(fd, &value, sizeof(value));
        if (ret < 0) {
            ret = -errno;
            warning("failed reading from %s [%s]",
                device, strerror(ret));
            return ret;
        }
        printf("received irq, GPIO state=%d\n", value);
    }

    close(fd);

    return 0;
}

static int run_read_value(struct smokey_test *t, int argc, char *const argv[])
{
    const char *device = NULL;
    int fd, ret, value = -1;

    smokey_parse_args(t, argc, argv);

    if (!SMOKEY_ARG_ISSET(read_value, device)) {
        warning("missing device= specification");
        return -EINVAL;
    }

    device = SMOKEY_ARG_STRING(read_value, device);
    fd = open(device, O_RDONLY|O_NONBLOCK);
    if (fd < 0) {
        ret = -errno;
        warning("cannot open device %s [%s]",
            device, strerror(ret));
        return ret;
    }

    if (!__T(ret, ioctl(fd, GPIO_RTIOC_DIR_IN)))
        return ret;

    ret = read(fd, &value, sizeof(value));
    close(fd);

    if (!__Tassert(ret == sizeof(value)))
        return -EINVAL;

    smokey_trace("value=%d", value);

    return 0;
}

static int run_write_value(struct smokey_test *t, int argc, char *const argv[])
{
    const char *device = NULL;
    int fd, ret, value;
    
    smokey_parse_args(t, argc, argv);

    if (!SMOKEY_ARG_ISSET(write_value, device)) {
        warning("missing device= specification");
        return -EINVAL;
    }

    device = SMOKEY_ARG_STRING(write_value, device);
    fd = open(device, O_WRONLY);
    if (fd < 0) {
        ret = -errno;
        warning("cannot open device %s [%s]",
            device, strerror(ret));
        return ret;
    }

    value = 1;
    if (!__T(ret, ioctl(fd, GPIO_RTIOC_DIR_OUT, &value)))
        return ret;
    
    ret = write(fd, &value, sizeof(value));
    close(fd);

    if (!__Tassert(ret == sizeof(value)))
        return -EINVAL;

    return 0;
}

int main(int argc, char *const argv[])
{
    struct smokey_test *t;
    int ret, fails = 0;

    if (pvlist_empty(&smokey_test_list))
        return 0;

    for_each_smokey_test(t) {
        ret = t->run(t, argc, argv);
        if (ret) {
            if (ret == -ENOSYS) {
                smokey_note("%s skipped (no kernel support)",
                        t->name);
                continue;
            }
            fails++;
            if (smokey_keep_going)
                continue;
            if (smokey_verbose_mode)
                error(1, -ret, "test %s failed", t->name);
            return 1;
        }
        smokey_note("%s OK", t->name);
    }

    return fails != 0;
}
